<?php
interface DataAccessOperations{

	/**
	 * 	Supprime un objet de la base
	 *
	 */
	function delete();

	/**
	 * 	Récupère un objet de la base, le crée et le renvoie
	 *	@param  int $id
	 */
	static function get($id);

	/**
	 * 	Récupère un tableau d'objets de la classe filtés par les paramètres passés
	 *
	 *	@param array $search_params
	 *	@param array $options
	 * 	@return array
	 */
	static function getAll($search_params,$options);

	/**
	 * 	Supprime les entrées de la table filtées par les paramètres passés
	 *
	 *	@param array $search_params
	 *	@param array $options
	 * 	@return boolean
	 */
	static function deleteAll($search_params);
}

/**
 * 	Objet mappant une table de la base de données (synchronisation automatique dans un sens, de l'objet vers
 * 	la base de données, l'objet n'est pas mis à jour si la base venait à changer)
 */
abstract class ModelObject implements DataAccessOperations{

	/**
	 * 	Tableau contenant les objets déja récupérés en base pour limiter les accès à la base
	 *
	 */
	protected static $cached_object_list = array();

	/**
	 * 	Identifiant de l'objet, correspond à la clé primaire de la table
	 *
	 */
	protected $id;

	/**
	 * 	Retourne la clé primaire de l'objet
	 *
	 */
	public function getId(){
		return $this->id;
	}

	/**
	 * 	Met à jour un objet dans la base (appel à UPDATE de SQL)
	 *
	 */
	protected abstract function update();

	/**
	 * 	Insère un objet dans la base (appel à INSERT de SQL)
	 *
	 */
	protected abstract function insert();

	/**
	 * Génère la clause WHERE d'une requête
	 * Le tableau en paramètre est de la forme suivante :
	 * Exemple:
	 * 	$search_params = array(
	 * 		0 => array( 'field'=>'id_dossier', 'operator'=>'BETWEEN', 'value'=>array(5,10) ),
	 *		1 => array( 'field'=>'id_affaire', 'operator'=>'=', 'value'=>7 )
	 *		2 => array( 'field'=>'id_affaire', 'operator'=>'ISNULL', 'value'=>null )
	 *		3 => array( 'field'=>'id_affaire','field_function'=>'COUNT([field])', 'operator'=>'=', 'value'=>1 )
	 * 	)
	 *
	 *
	 * @param array $search_params
	 * @return string
	 */
	protected static function generateWhereClause (array $search_params) {
		if (!empty($search_params)) {
			$pieces = array();
			$cpt = 0;
			foreach ($search_params as $ind => $mix) {
				$field = strtolower(trim($mix['field']));
				$placeholder_name = preg_replace('/[^a-zA-Z0-9_]+/','_',$field);
				$field = "`".$field."`";
				$operator = strtoupper(trim($mix['operator']));
				$field_function = null;
				if(isset($mix['field_function'])){
					$field_function = strtoupper(trim($mix['field_function']));
				}
				if(!is_null($field_function)){
					$field = preg_replace('/\[FIELD\]/',$field,$field_function);
				}
				$value = $mix['value'];
				if ((!is_null($field) && !empty($field)) && ((!is_null($operator) && !empty($operator)))) {
					if(!is_null($operator) && !empty($operator) && in_array($operator,array('=','<','>','>=','<=','!=','BETWEEN','LIKE','IN','IS NULL','IS NOT NULL'))){
						if($operator === 'BETWEEN'){
							$pieces[] = $field." ".$operator." :".$placeholder_name.$cpt."_0 AND :".$placeholder_name.$cpt."_1";
						}
						else if($operator === 'IN'){
							$in_clause =  $field." ".$operator." (";
							$i = 0;
							foreach($value as $key => $val){
								$in_clause .= " :".$placeholder_name.$cpt."_".$i.", ";
								$i++;
							}
							if($i == 0){
								throw new Exception("La clause WHERE est invalide, l'opérateur IN doit contenir au moins une valeur");
							}
							$pieces[] = substr($in_clause,0,-2).")";
						}
						else if($operator === 'IS NULL' || $operator === 'IS NOT NULL'){
							$pieces[] = $field." ".$operator;
						}
						else{
							$pieces[] = $field." ".$operator." :".$placeholder_name.$cpt;
						}
					}
					else{
						throw new Exception("La clause WHERE est invalide, le tableau \$search_params contient un opérateur invalide : ".$operator);
					}
				}
				else {
					throw new Exception("La clause WHERE est invalide, le tableau \$search_params n'est pas rempli correctement");
				}
				$cpt++;
			}

			return " WHERE " . implode(' AND ', $pieces);
		}
		return "";
	}

	/**
	 * Génère les clauses GROUP BY, ORDER BY and LIMIT d'une requête
	 *
	 * @param array $options
	 * @return string
	 */
	protected static function generateOptionClause (array $options) {
		$query = "";

		if (!empty($options['group by'])) {
			$pieces = array();

			foreach((array)$options['group by'] as $field){
				$pieces[] = "`".$field."`";
			}

			$query .= " GROUP BY ".implode(',' ,$pieces);
		}

		if (!empty($options['order by'])) {
			$pieces = array();

			foreach($options['order by'] as $field){
				$pieces[] = "`".$field."`";
			}

			$query .= " ORDER BY ".implode(',', $pieces);

			if (isset($options['order by type']) && (strtoupper($options['order by type']) == 'ASC' || strtoupper($options['order by type']) == 'DESC')){
				$query .= " " . strtoupper($options['order by type']);
			}
		}

		if (!empty($options['limit'])) {
			if (count($options['limit']) == 1) {
				$options['limit'] = (array)$options['limit'];
				$query .= " LIMIT ".$options['limit'][0];
			}

			if (count($options['limit']) == 2) {
				$query .= " LIMIT ".$options['limit'][0].",".$options['limit'][1];
			}
		}
		return $query;
	}

	/**
	 * Permet d'appeler des méthodes de la forme getBy'nomDeColonne'
	 * (il faut remplacer les underscore par des majuscules type_navire => TypeNavire)
	 * exemple : Navire::getByNomNavire("Mon bateau") renverra tous les objets Navire dont le nom est "Mon bateau".
	 *
	 * @param string $name
	 * @param array $arguments
	 * @return array
	 */
	public static function __callStatic($name, $arguments){
		if(method_exists(__CLASS__,$name)){
			return static::$name();
		}
		else{
			if(startsWith($name,"getBy")){
				$search_params = array();
				$field = substr($name,5);
				while(preg_match("/^(.+)([A-Z])/",$field) !== 0){
					$field = preg_replace_callback("/^(.+)([A-Z])/",function($match){
						return $match[1]."_".lcfirst($match[2]);
					},$field);
				}
				$field = strtolower($field);
				$search_params[] = array( "field" => $field, "operator" => "LIKE" , "value" => $arguments[0]);
				$t = static::getAll($search_params);
				return $t;
			}
			else{
				throw new Exception("Appel d'une méthode inexistante de la classe ".get_called_class());
			}
		}
	}

	/**
	 * 	Récupère un tableau de chaines correspondant aux valeurs de l'enum passé
	 *
	 *	@param string $enum_name
	 *	@param string $table_name
	 * 	@return array
	 */
	static function getEnumValues($enum_name,$table_name){
		$dbh = SingletonPDO::getInstance();
		try{
			$dbh->beginTransaction();
			$sql = 'SELECT column_type FROM `information_schema`.columns WHERE table_schema = :table_schema AND table_name = :table_name AND column_name = :enum_name';
			$stmt = $dbh->prepare($sql);
			$stmt->execute(array(':table_schema' => DB_NAME, ':table_name'=>$table_name, ':enum_name' => $enum_name));
			if ($row = $stmt->fetch()){
				$enum = substr( $row['column_type'], 6, -2);
				$enum_values = explode( "','", $enum );
				$dbh->commit();
				return $enum_values;
			}
			else {
				$dbh->commit();
				$dbh->beginTransaction();
				return null;
			}
		}
		catch(Exception $e) {
			$dbh->rollBack();
			throw new Exception("La récupération des valeurs pour l'enum ".$enum_name." dans la table ".$table_name." a échouée",0,$e);
		}
	}


	/**
	 * Génère une chaine contenant le nom de la classe puis de ses attributs et valeurs de ceux-ci
	 *	Exemple:
	 * 	Dossier{
	 * 		id_dossier : 55
	 * 		id_affaire : 41
	 * 		id_patient : 800
	 * 		id_cas : 54
	 * 	}
	 */
	public function __toString(){
		$reflect = new ReflectionClass($this);
		$props = $reflect->getProperties();
		$str = "\n".$reflect->getName()."{\n";
		foreach ($props as $prop) {
			$prop->setAccessible(true);
			$str .=  "\t".$prop->getName()." : ". $prop->getValue($this)."\n";
		}
		$str .= "}\n";
		return $str;
	}

	protected static function getFromCache($id){
		if(static::existsInCache($id)){
			return static::$cached_object_list[$id];
		}
		else{
			return null;
		}
	}

	protected static function existsInCache($id){
		return key_exists($id,static::$cached_object_list);
	}

	protected static function cacheObject(ModelObject $object){
		if(get_class($object) == get_called_class()){
			static::$cached_object_list[$object->getId()] = $object;
		}
	}

	protected static function removeObjectFromCache($id){
		if(static::existsInCache($id)){
			unset(static::$cached_object_list[$id]);
		}
	}

	public static function setCachedObjectList(array $array){
		static::$cached_object_list = $array;
	}
}
?>